跳到主要内容

Go 的 runtime 包

官方文档

runtime 包是干什么的?

包运行时包含与 Go 的运行时系统交互的操作,例如控制 GoRoutine 的函数。说白了就是获取一些系统信息

GOMAXPROCS 设置核数

设置最大的可同时使用的 CPU 核数,通过 runtime.GOMAXPROCS 函数,应用程序何以在运行期间设置运行时系统中得 P 最大数量。但这会引起 “Stop the World”。所以,应在应用程序最早的调用。并且最好是在运行 Go 程序之前设置好操作程序的环境变量 GOMAXPROCS,而不是在程序中调用 runtime.GOMAXPROCS 函数。无论我们传递给函数的整数值是什么值,运行时系统的P最大值总会在 1~256 之间。

同时可以利用 NumCPU 返回当前系统的 CPU 核数量

func init(){
//1.获取逻辑cpu的数量
fmt.Println("逻辑CPU的核数:",runtime.NumCPU())
//2.设置go程序执行的最大的:[1,256]
n := runtime.GOMAXPROCS(runtime.NumCPU())
fmt.Println(n)
}

Gosched 让出线程

让当前线程让出 cpu 以让其它线程运行,它不会挂起当前线程,因此当前线程未来会继续执行,这个函数的作用是让当前 goroutine 让出 CPU,当一个 goroutine 发生阻塞,Go 会自动地把与该 goroutine 处于同一系统线程的其他 goroutine 转移到另一个系统线程上去,以使这些 goroutine 不阻塞。

func main() {
go func() {
for i := 0; i < 5; i++ {
fmt.Println("goroutine....")
}
}()

for i := 0; i < 4; i++ {
//让出时间片,先让别的协议执行,它执行完,再回来执行此协程
runtime.Gosched()
fmt.Println("main....")
}
}

打印输出

goroutine....
goroutine....
goroutine....
goroutine....
goroutine....
main....
main....
main....
main....

Goexit 退出协程

退出当前 goroutine(但是 defer 语句会照常执行)

func main() {
//创建新建的协程
go func() {
fmt.Println("goroutine开始...")

//调用了别的函数
fun()

fmt.Println("goroutine结束...")
}() //别忘了()

//睡一会儿,不让主协程结束
time.Sleep(3 * time.Second)
}

func fun() {
defer fmt.Println("defer...")

//return //终止此函数
runtime.Goexit() //终止所在的协程

fmt.Println("fun函数....")
}

打印输出

goroutine开始...
defer...

NumGoroutine 协程数目

返回正在执行和排队的任务总数,runtime.NumGoroutine 函数在被调用后,会返回系统中的处于特定状态的 Goroutine 的数量。这里的特指是指 Grunnable\Gruning\Gsyscall\Gwaition。处于这些状态的 Groutine 即被看做是活跃的或者说正在被调度。

注意:垃圾回收所在 Groutine 的状态也处于这个范围内的话,也会被纳入该计数器。

runtime.GC 主动 GC

会让运行时系统进行一次强制性的垃圾收集

  1. 强制的垃圾回收:不管怎样,都要进行的垃圾回收。
  2. 非强制的垃圾回收:只会在一定条件下进行的垃圾回收(即运行时,系统自上次垃圾回收之后新申请的堆内存的单元(也成为单元增量)达到指定的数值)。

取得 GOROOT 和 GOOS

获取 goroot 目录

func Pass() {
//获取 goroot 目录:
fmt.Println("GOROOT-->", runtime.GOROOT())

//获取操作系统
fmt.Println("os/platform-->", runtime.GOOS) // GOOS--> darwin,mac系统
}

Caller 返回函数栈信息

func Caller(skip int) (pc uintptr, file string, line int, ok bool)

参数:skip是要提升的堆栈帧数,0-当前函数,1-上一层函数,.... 返回值:

  • pc 是 uintptr 这个返回的是函数指针
  • file 是函数所在文件名目录
  • line 所在行号
  • ok 是否可以获取到信息

使用例:

func main() {
for i := 0; i < 4; i++ {
test(i) // 13
}
}

func test(skip int) {
call(skip) // 18
}

func call(skip int) {
pc, file, line, ok := runtime.Caller(skip) // 22
pcName := runtime.FuncForPC(pc).Name() //获取函数名
fmt.Println(fmt.Sprintf("%v %s %d %t %s", pc, file, line, ok, pcName))
}

打印输出:

4717194   /home/alsritter/Desktop/co/test/sterror/main.go   22   true   main.call
4717092 /home/alsritter/Desktop/co/test/sterror/main.go 18 true main.test
4717085 /home/alsritter/Desktop/co/test/sterror/main.go 13 true main.main
4399206 /data/home/alsritter/sdk/go1.17.3/src/runtime/proc.go 255 true runtime.main

这个 Caller 还有一个类似的函数

func Callers(skip int, pc []uintptr) int 

这个方法传入一个 slice,Callers 方法会自动填满这个 slice,前面那个参数 skip 表示跳过几个栈方法

// 取出调用栈50个数据
pcSlice := make([]uintptr, 50)
count := runtime.Callers(2, pcSlice)

然后可以通过另一个方法根据取得的函数指针取得这些函数属性

// 取出调用栈50个数据
pcSlice := make([]uintptr, 50)
count := runtime.Callers(2, pcSlice)
pcSlice = pcSlice[:count] // 可能没有 50 个

// 返回的 *Frames 类似一个迭代器
frames := runtime.CallersFrames(pcSlice)
var frame runtime.Frame
more := count > 0
for more {
frame, more = frames.Next()
fmt.Println(fmt.Sprintf("%v %s %d %t %s", frame.PC, frame.File, frame.Line, frame.Function))
}

References

GO语言基础进阶教程:runtime包 - 茹姐的文章 - 知乎 Golang中的runtime.Caller理解